﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Collections;
using System.Reflection;
using System.Windows.Forms;
using System.Threading;
using Noris.Tools.TraceVisualiser.Interface;
using Noris.Tools.TraceVisualiser.Forms;

namespace Noris.Tools.TraceVisualiser.Support
{
    // CLASS Steward
    /// <summary>
    /// Hlavní správce podkladových dat aplikace.
    /// Používá se pro získání dat trvalého charakteru i pro volání systémových metod.
    /// </summary>
    /// <remarks>
    /// Obsahuje privátní singleton na svoji instanci, v níž uchovává trvalá data.
    /// Uchovává v sobě konfiguraci, reflexi výkonných tříd.
    /// </remarks>
    public partial class Steward
    {
        #region Singleton na svoji instanci
        /// <summary>
        /// Privátní přístup k instančním datům stewarda
        /// </summary>
        private static Steward Current
        {
            get
            {
                if (_Current != null) return _Current;
                lock (_LockerWrite)
                {
                    if (_Current == null)
                    {
                        Steward current = Steward.Create();
                        _Current = current;
                        // Od teď je Steward funkční (dotaz na Steward.Current vrátí instanci singletonu).
                    }
                }
                return _Current;
            }
        }
        /// <summary>
        /// Statické úložiště pro instanci
        /// </summary>
        private static Steward _Current;
        /// <summary>
        /// Locker
        /// </summary>
        private static object _LockerWrite = new object();
        /// <summary>
        /// Konstruktor je private, zvenku si stewarda nikdo nevyrobí
        /// </summary>
        private Steward() { }
        /// <summary>
        /// Privátní tvorba stevarda
        /// </summary>
        private static Steward Create()
        {
            Steward steward = new Steward();

            // Zde vytvářené objekty NESMÍ používat Stewarda, ten v tuto chvíli neexistuje:
            steward._ServiceMaps = new Dictionary<Type, Dictionary<Type, object>>();
            steward._ServiceWriteLock = new object();
            steward._AppConfig = new Config();               // Konstruktor nepoužívá Stewarda. Potřebuje jej metoda COnfig.Load(), a ta je volaná až později (this._PrepareConfig).
            steward._Cache = new Dictionary<Type, object>();


            return steward;
        }
        #endregion
        #region Start a konec aplikace (load, release), RunGUI, GUI Thread
        /// <summary>
        /// Povinná metoda. kdo ji nezavolá, ten nepojede.
        /// V parametrech je třeba předat soupis typů služeb, které chceme, aby steward registroval a uměl obsloužit (najít a vracet).
        /// </summary>
        /// <param name="findServiceTypes">Soupis služeb, které budeme používat (které chceme v rámci aplikace detekovat)</param>
        public static void AppInit(string[] arguments)
        {
            WinReg.SetRootDefaultFolder(@"Software\Lcs\TraceVisualiser");
            Current._ParamStore(arguments);
            Current._PrepareConfig();
            Current._ServicesLoad();
            Current._FunctionMenuInit();
            Current._AppReady = true;
        }
        /// <summary>
        /// Spustí GUI aplikace (main okno)
        /// </summary>
        public static void RunGUI(Type mainFormType)
        {
            try
            {
                Application.EnableVisualStyles();
                Application.SetCompatibleTextRenderingDefault(false);

                object mainForm = System.Activator.CreateInstance(mainFormType);
                if (!(mainForm is Form))
                    return;
                Form form = mainForm as Form;
                ApplicationContext appc = new ApplicationContext(form);
                Current._GuiThread = System.Threading.Thread.CurrentThread;
                Current._GuiThread.Name = "Main GUI Thread";
                MainForm = form;

                // Status bar:
                StatbusBarSetInit();

                MemoryGcFinish();

                Application.Run(appc);   // Tady stojí toto vlákno až do skončení aplikace. V případě vizuální akce ji zdejší vlákno obsluhuje.
            }
            catch (Exception exc)
            {
                string msg = GetExceptionMessage(exc) + Environment.NewLine + GetInnerStack(exc);
                Msg.Error("Chyba při běhu aplikace: " + msg);
            }
            finally
            {
                MainForm = null;
                Current._GuiThread = null;
            }
        }
        /// <summary>
        /// Vyčistí paměť
        /// </summary>
        public static void MemoryGcFinish()
        {
            long memPrev = System.GC.GetTotalMemory(false);
            System.GC.Collect();
            long memAftN = System.GC.GetTotalMemory(true);

            long disposed = memPrev - memAftN;

            decimal megaByte = Math.Round(((decimal)memAftN) / (1024M * 1024M), 2);
            StatbusBarSet(StatusBarItem.MemoryText, "Used: " + megaByte.ToString("### ##0.00") + " MB");
        }
        /// <summary>
        /// Povinná metoda. Je třeba ji zavolat před koncem aplikace.
        /// </summary>
        public static void AppDone()
        {
            Current._SaveConfig();
            Current._FunctionMenuDispose();
            Current._CacheDispose();
            Current._ServicesUnLoad();
            Current._TrayIconDispose();
            Current._AppReady = false;
        }
        /// <summary>
        /// Příznak, že je korektně inicializována aplikace, a je možno používat ostatní metody Stewarda.
        /// Na true se nastaví provedením metody AppInit(), nikoli pouhým instancováním Stewarda !!!
        /// </summary>
        public static bool AppReady { get { return Current._AppReady; } }
        /// <summary>
        /// Thread GUI
        /// </summary>
        public static Thread GuiThread { get { return Current._GuiThread; } }
        /// <summary>
        /// GUI Thread
        /// </summary>
        private Thread _GuiThread;
        /// <summary>
        /// Příznak, že je korektně inicializována aplikace, a je možno používat ostatní metody Stewarda.
        /// Na true se nastaví provedením metody AppInit(), nikoli pouhým instancováním Stewarda !!!
        /// </summary>
        private bool _AppReady;
        #endregion
		#region Main Form, MDI, status bar, CustomColors, TraceColors, Debug mode
		/// <summary>
		/// Reference na Main okno aplikace.
		/// </summary>
		public static Form MainForm
		{
			get
			{
				Form mainForm = Current._MainForm;
				if (mainForm == null) return null;
				if (mainForm.IsDisposed) return null;
				return mainForm;
			}
			private set
			{
				Current._MainForm = value;
			}
		}
        /// <summary>
        /// Zobrazí daný Form v threadu MainFormu (=GUI thread), a toto okno (childForm) zobrazí s parametrem owner = MainForm.
        /// </summary>
        /// <param name="childForm"></param>
        public static void ShowFormInGuiThread(Form childForm)
        {
            System.Windows.Forms.Form mainForm = MainForm;
            if (mainForm == null || !mainForm.IsMdiContainer) return;

            if (mainForm.InvokeRequired)
                mainForm.BeginInvoke(new Action<Form, Form>(_ShowFormInGuiThread), mainForm, childForm);
            else
                _ShowFormInGuiThread(mainForm, childForm);
        }
        private static void _ShowFormInGuiThread(Form mainForm, Form childForm)
        {
            childForm.ShowInTaskbar = false;
            childForm.Show(mainForm);
        }
        /// <summary>
        /// Najde MainForm, a pokud existuje a je to IsMdiContainer, pak do něj přidá daný formulář jako Child.
        /// Formulář zobrazí.
        /// Pokud to vše vyjde, vrátí true.
        /// Pokud nenajde MainForm jako MdiContainer, vrátí false.
        /// </summary>
        /// <param name="childForm"></param>
        public static bool MdiChildShowNew(Form childForm)
        {
            return _MdiChildAddShowNew(childForm, true);
        }
		/// <summary>
		/// Najde MainForm, a pokud existuje a je to IsMdiContainer, pak do něj přidá daný formulář jako Child.
		/// Pokud to vše vyjde, vrátí true.
		/// Pokud nenajde MainForm jako MdiContainer, vrátí false.
		/// </summary>
		/// <param name="childForm"></param>
		public static bool MdiChildAddNew(Form childForm)
		{
            return _MdiChildAddShowNew(childForm, false);
        }
        private static bool _MdiChildAddShowNew(Form childForm, bool show)
        {
			if (childForm == null) return false;

			System.Windows.Forms.Form mainForm = MainForm;
			if (mainForm == null || !mainForm.IsMdiContainer) return false;

            if (mainForm.InvokeRequired)
                mainForm.BeginInvoke(new Action<Form, Form, bool>(_ShowNewMdiChild), mainForm, childForm, show);
            else
                _ShowNewMdiChild(mainForm, childForm, show);

			return true;
		}
        private static void _ShowNewMdiChild(Form mdiParent, Form mdiChild, bool show)
        {
            if (mdiParent.InvokeRequired)
                mdiParent.BeginInvoke(new Action<Form, Form, bool>(_ShowNewMdiChild), mdiParent, mdiChild, show);
            else
            {
                mdiChild.MdiParent = mdiParent;
                mdiChild.Show();
            }
        }
        /// <summary>
        /// Inicializuje StatusBar:
        /// Vepíše ikonu programu a číslo verze, nastaví klidový stav (text Ready, zhasne Progress).
        /// </summary>
        /// <param name="item"></param>
        /// <param name="value"></param>
        public static void StatbusBarSetInit()
        {
            StatbusBarSet(StatusBarItem.Icon, Pics.PicLibrary16.GnomePlanner);
            StatbusBarSet(StatusBarItem.Version, "TraceVisualiser " + Program.Version);
            StatbusBarSetReady();
        }
        /// <summary>
        /// Nastaví StatusBar na standardní klidový stav:
        /// Hlavní text ukazuje "Ready", ukazatel je zhasnutý.
        /// </summary>
        /// <param name="item"></param>
        /// <param name="value"></param>
        public static void StatbusBarSetReady()
        {
            StatbusBarSet(Forms.StatusBarItem.Progress, null);             // Zhasne progress
            StatbusBarSet(Forms.StatusBarItem.MainText, "Ready");
        }
        /// <summary>
		/// Pošle do status baru main okna aplikace, do dané položky danou hodnotu
		/// </summary>
		/// <param name="item"></param>
		/// <param name="value"></param>
		public static void StatbusBarSet(StatusBarItem item, object value)
		{
			Form mainForm = MainForm;
			if (mainForm == null) return;
			if (mainForm is IFormStatusbar)
			{
				IFormStatusbar iStatus = mainForm as IFormStatusbar;
				if (iStatus != null)
					iStatus.StatusBarSet(item, value);
			}
		}
		/// <summary>
		/// Instance main okna aplikace
		/// </summary>
		private Form _MainForm;
        /// <summary>
        /// Úložiště pro pole CustomColors, které se používají v ColorDialogu.
        /// Jen pro informaci: barvy jsou ukládány jako Int32, ale v jiném formátu než je Colors.ToArgb() !!!
        /// </summary>
        public static int[] CustomColors
        {
            get { return _Current._CustomColors; }
            set { _Current._CustomColors = value; }
        }
        private int[] _CustomColors;
        /// <summary>
        /// Obsahuje true, pokud aplikace běží v Debug režimu (je spuštěna z Visual studia)
        /// </summary>
        public static bool DebugMode
        {
            get
            {
                if (!Current._DebugMode.HasValue)
                    Current._DetectDebugMode();
                bool? debugMode = Current._DebugMode;
                return (debugMode.HasValue && debugMode.Value);
            }
        }
        /// <summary>
        /// Příznak, že TraceVisualiser je spuštěn z TraceAnalyzeru s předáním korektního licenčního klíče
        /// </summary>
        public static bool IsStartedFromTA
        {
            get { return Current._StartFromTA; }
            set { Current._StartFromTA = value; }
        }
        private bool _StartFromTA;
        /// <summary>
        /// Připraví proměnné pro start TraceVisualiseru z TraceAnalyseru
        /// </summary>
        public static void PrepareStartFromTA()
        {
            // Steward.AppConfig.BackgroundMode = Config.MainBackgroundMode.StandardTraceAnalyser;
        }
        /// <summary>
        /// Barva pozadí standardní pro TraceAnalyser
        /// </summary>
        public static System.Drawing.Color TraceAnalyserBackColor { get { return System.Drawing.Color.FromArgb(0x555555); } }
        /// <summary>
        /// Vrací adresář k datům aplikace, typicky: C:\Users\daj\AppData\Roaming\Asseco Solutions\TraceVisualiser
        /// Adresář neobsahuje verzi aplikace.
        /// </summary>
        public static string UserAppDataPath
        {
            get
            {
                System.IO.DirectoryInfo di = new DirectoryInfo(System.IO.Path.GetDirectoryName(System.Windows.Forms.Application.UserAppDataPath));          // Odstraním číslo verze !!!
                if (!di.Exists)
                    di.Create();
                return di.FullName;
            }
        }
        /// <summary>
        /// Metoda vrátí daný adresář (subPath) v rámci adresáře Steward.UserAppDataPath.
        /// Metoda zajistí, že daný adresář bude existovat.
        /// </summary>
        /// <param name="subPath"></param>
        /// <returns></returns>
        public static string GetUserAppDataPath(string subPath)
        {
            string path = System.IO.Path.Combine(Steward.UserAppDataPath, subPath);
            if (!System.IO.Directory.Exists(path))
                System.IO.Directory.CreateDirectory(path);
            return path;
        }
        private void _DetectDebugMode()
        {
            this._DebugMode = System.Diagnostics.Debugger.IsAttached;
        }
        private bool? _DebugMode;
		#endregion
        #region Parametry spuštěné aplikace
        /// <summary>
        /// Vrátí příznak, zda existuje parametr daného klíče.
        /// Klíč je text uvedený před textem parametru, například:
        /// Parametry spuštění jsou    /C=data.config /User=lcs
        /// Pak klíčem je text "/C" nebo "/User", pak vrací true.
        /// Pokud parametr nenajde, vrátí false.
        /// </summary>
        /// <param name="key"></param>
        /// <param name="ignoreCase"></param>
        /// <returns></returns>
        public static bool ParamExists(string key, bool ignoreCase)
        {
            return (Current._ParamCount(new string[] { key }, ignoreCase) > 0);
        }
        /// <summary>
        /// Najde a vrátí parametr, podle jeho daného klíče.
        /// Klíč je text uvedený před textem parametru, například:
        /// Parametry spuštění jsou    /C=data.config /User=lcs
        /// Pak klíčem je text "/C" nebo "/User", vrácenou hodnotou pak "data.config" nebo "lcs".
        /// Pokud parametr nenajde, vrátí null.
        /// Pokud parametr existuje, a je prázdný, vrací String.Empty
        /// Pokud chceme otestovat existenci, zavoláme metodu ParamExists(), nebo ParamCount().
        /// Pokud chceme získat více parametrů. použijeme metodu ParamsGet().
        /// </summary>
        /// <param name="key">Klíč parametru = text před rovnítkem (při očekávaném zadání např. /User=lcs je klíčem text "/User")</param>
        /// <param name="ignoreCase">Ignorovat velikost písmen, tzn. "/User" najde parametry: "/user" i "/USER"</param>
        /// <returns></returns>
        public static string ParamGet(string key, bool ignoreCase)
        {
            return Current._ParamGet(new string[] { key }, ignoreCase);
        }
        /// <summary>
        /// Najde a vrátí parametr, podle jeho daného klíče.
        /// Klíč je text uvedený před textem parametru, například:
        /// Parametry spuštění jsou    /C=data.config /User=lcs
        /// Pak klíčem je text "/C" nebo "/User", vrácenou hodnotou pak "data.config" nebo "lcs".
        /// Pokud parametr nenajde, vrátí null.
        /// Pokud parametr existuje, a je prázdný, vrací String.Empty
        /// Pokud chceme otestovat existenci, zavoláme metodu ParamExists(), nebo ParamCount().
        /// Pokud chceme získat více parametrů. použijeme metodu ParamsGet().
        /// </summary>
        /// <param name="keyList">Hledané parametry, vrací se první z nalezených. Klíč parametru = text před rovnítkem (při očekávaném zadání např. /User=lcs je klíčem text "/User")</param>
        /// <param name="ignoreCase">Ignorovat velikost písmen, tzn. "/User" najde parametry: "/user" i "/USER"</param>
        /// <returns></returns>
        public static string ParamGet(string[] keyList, bool ignoreCase)
        {
            return Current._ParamGet(keyList, ignoreCase);
        }
        /// <summary>
        /// Najde a vrátí parametry, které vyhovují danému klíči (daným klíčům), vrací je včetně jejich klíče.
        /// Klíč je text uvedený před textem parametru, například:
        /// Parametry spuštění jsou    /C=data.config /User=lcs
        /// Pak klíčem je text "/C" nebo "/User", vrácenou hodnotou pak "data.config" nebo "lcs".
        /// Pokud parametr nenajde, vrátí null.
        /// Pokud parametr existuje, a je prázdný, vrací String.Empty
        /// Pokud chceme otestovat existenci, zavoláme metodu ParamExists(), nebo ParamCount().
        /// Pokud chceme získat více parametrů. použijeme metodu ParamsGet().
        /// </summary>
        /// <param name="key">Klíč parametru = text před rovnítkem (při očekávaném zadání např. /User=lcs je klíčem text "/User")</param>
        /// <param name="ignoreCase">Ignorovat velikost písmen, tzn. "/User" najde parametry: "/user" i "/USER"</param>
        /// <returns></returns>
        public static List<KeyValuePair<string, string>> ParamsGet(string key, bool ignoreCase)
        {
            return Current._ParamsGet(new string[] { key }, ignoreCase);
        }
        /// <summary>
        /// Najde a vrátí parametry, které vyhovují danému klíči (daným klíčům), vrací je včetně jejich klíče.
        /// Klíč je text uvedený před textem parametru, například:
        /// Parametry spuštění jsou    /C=data.config /User=lcs
        /// Pak klíčem je text "/C" nebo "/User", vrácenou hodnotou pak "data.config" nebo "lcs".
        /// Pokud parametr nenajde, vrátí null.
        /// Pokud parametr existuje, a je prázdný, vrací String.Empty
        /// Pokud chceme otestovat existenci, zavoláme metodu ParamExists(), nebo ParamCount().
        /// Pokud chceme získat více parametrů. použijeme metodu ParamsGet().
        /// </summary>
        /// <param name="keyList">Hledané parametry, vrací se první z nalezených. Klíč parametru = text před rovnítkem (při očekávaném zadání např. /User=lcs je klíčem text "/User")</param>
        /// <param name="ignoreCase">Ignorovat velikost písmen, tzn. "/User" najde parametry: "/user" i "/USER"</param>
        /// <returns></returns>
        public static List<KeyValuePair<string, string>> ParamsGet(string[] keyList, bool ignoreCase)
        {
            return Current._ParamsGet(keyList, ignoreCase);
        }
        /// <summary>
        /// Najde a vrátí počet parametrů, které odpovídají danému klíči / klíčům.
        /// Klíč je text uvedený před textem parametru, například:
        /// Parametry spuštění jsou    /C=data.config /User=lcs
        /// Pak klíčem je text "/C" nebo "/User", vrácenou hodnotou pak "data.config" nebo "lcs".
        /// Pokud parametr nenajde, vrátí 0.
        /// </summary>
        /// <param name="key">Klíč parametru = text před rovnítkem (při očekávaném zadání např. /User=lcs je klíčem text "/User")</param>
        /// <param name="ignoreCase">Ignorovat velikost písmen, tzn. "/User" najde parametry: "/user" i "/USER"</param>
        /// <returns></returns>
        public static int ParamCount(string key, bool ignoreCase)
        {
            return Current._ParamCount(new string[] { key }, ignoreCase);
        }
        /// <summary>
        /// Najde a vrátí počet parametrů, které odpovídají danému klíči / klíčům.
        /// Klíč je text uvedený před textem parametru, například:
        /// Parametry spuštění jsou    /C=data.config /User=lcs
        /// Pak klíčem je text "/C" nebo "/User", vrácenou hodnotou pak "data.config" nebo "lcs".
        /// Pokud parametr nenajde, vrátí 0.
        /// </summary>
        /// <param name="keyList">Hledané parametry, vrací se první z nalezených. Klíč parametru = text před rovnítkem (při očekávaném zadání např. /User=lcs je klíčem text "/User")</param>
        /// <param name="ignoreCase">Ignorovat velikost písmen, tzn. "/User" najde parametry: "/user" i "/USER"</param>
        /// <returns></returns>
        public static int ParamCount(string[] keyList, bool ignoreCase)
        {
            return Current._ParamCount(keyList, ignoreCase);
        }
        /// <summary>
        /// Parametry spuštění.
        /// Key je hodnota před rovnítkem, Value je hodnota za rovnítkem.
        /// Pokud argument neobsahuje rovnítko, pak je Key prázdné.
        /// Nejde o Dictionary, jde tedy možno mít více argumentů se shodným Key (nebo bez Key).
        /// </summary>
        public static List<KeyValuePair<string, string>> ParamList { get { return new List<KeyValuePair<string, string>>(Current._ParamList); } }
        #region Parametry spuštěné aplikace - private
        /// <summary>
        /// Parametry spuštění.
        /// Key je hodnota před rovnítkem, Value je hodnota za rovnítkem.
        /// Pokud argument neobsahuje rovnítko, pak je Key prázdné.
        /// Nejde o Dictionary, jde tedy možno mít více argumentů se shodným Key (nebo bez Key).
        /// </summary>
        private List<KeyValuePair<string, string>> _ParamList;
        /// <summary>
        /// Uloží do sebe parametry předané při spouštění aplikace
        /// </summary>
        /// <param name="arguments"></param>
        private void _ParamStore(string[] arguments)
        {
            this._ParamList = new List<KeyValuePair<string, string>>();
            if (arguments == null) return;

            foreach (string argument in arguments)
            {
                string text = argument.Trim();
                int posE = text.IndexOf("=");              // Kde je rovnítko
                int posU = text.IndexOf("\"");             // Kde je uvozovka
                if (posE >= 0 && posU >= 0 && posU < posE) // Pokud je tam obojí, ale uvozovka je dřív než rovnítko, pak jako by rovnítko nebylo
                    posE = -1;
                string key = String.Empty;
                string value = "";
				if (posE < 0)
					key = text;
                else if (posE == 0)
                    key = text.Substring(1);
                else if (posE > 0)
                {
                    key = text.Substring(0, posE).Trim();
                    value = text.Substring(posE + 1).Trim();
                }
                this._ParamList.Add(new KeyValuePair<string, string>(key, value));
            }
        }
        /// <summary>
        /// Metoda najde a vrátí první parametr spuštění, který odpovídá danému klíči.
        /// Pokud nenajde nic, vrací null.
        /// </summary>
        /// <param name="key"></param>
        /// <param name="ignoreCase"></param>
        /// <returns></returns>
        private string _ParamGet(string key, bool ignoreCase)
        {
            return _ParamGet(new string[] { key }, ignoreCase);
        }
        /// <summary>
        /// Metoda najde a vrátí první parametr spuštění, který odpovídá danému klíči.
        /// Pokud nenajde nic, vrací null.
        /// </summary>
        /// <param name="keyList"></param>
        /// <param name="ignoreCase"></param>
        /// <returns></returns>
        private string _ParamGet(string[] keyList, bool ignoreCase)
        {
            List<KeyValuePair<string, string>> values = _ParamsGet(keyList, ignoreCase);
            if (values.Count == 0) return null;
            return values[0].Value;
        }
        /// <summary>
        /// Metoda určí počet parametrů s daným klíčem.
        /// Lze využít pro detekci existence určitého parametru.
        /// </summary>
        /// <param name="keyList"></param>
        /// <param name="ignoreCase"></param>
        /// <returns>Počet odpovídajících parametrů</returns>
        private int _ParamCount(string[] keyList, bool ignoreCase)
        {
            List<KeyValuePair<string, string>> values = _ParamsGet(keyList, ignoreCase);
            return values.Count;
        }
        /// <summary>
        /// Metoda najde a vrátí všechny parametry spuštění, které odpovídají danému klíči.
        /// </summary>
        /// <param name="keyList"></param>
        /// <param name="ignoreCase"></param>
        /// <returns></returns>
        private List<KeyValuePair<string, string>> _ParamsGet(string[] keyList, bool ignoreCase)
        {
            List<KeyValuePair<string, string>> values = new List<KeyValuePair<string, string>>();
            if (_ParamList != null)
            {
                foreach (KeyValuePair<string, string> param in _ParamList)
                {
                    bool equal = false;
                    if (keyList == null || keyList.Length == 0)
                        equal = true;
                    else
                    {
                        foreach (string key in keyList)
                        {
                            bool equalOne = (ignoreCase ? String.Equals(param.Key, key, StringComparison.OrdinalIgnoreCase) : String.Equals(param.Key, key, StringComparison.Ordinal));
                            if (equalOne)
                            {
                                equal = true;
                                break;
                            }
                        }
                    }
                    if (equal)
                        values.Add(param);
                }
            }
            return values;
        }
        #endregion
        #endregion
        #region Config
        /// <summary>
        /// Objekt s konfigurací aplikace
        /// </summary>
        public static Config AppConfig { get { return Current._AppConfig; } }
        private Config _AppConfig;
        /// <summary>
        /// Načte konfiguraci. Nyní je Steward již funkční a lze jej používat.
        /// </summary>
        private void _PrepareConfig()
        {
            string configFile = this._ParamGet("C", true);
            if (String.IsNullOrEmpty(configFile))
                configFile = System.IO.Path.Combine(Steward.UserAppDataPath, "Lcs.TraceVisualiser.config");
            this._AppConfig.Load(configFile);
        }
        private void _SaveConfig()
        {
            this._AppConfig.Save();
        }
        #endregion
        #region Vyhledání služeb, vyhledání poskytovatelů služeb, tvorba poskytovatele
        /// <summary>
        /// Vrátí soupis TYPŮ poskytovatelů služeb daného druhu.
        /// Vstupem je typ služby = určení interface, který má služba (servisní třída) nabízet.
        /// Tento typ služby musí být jedním z těch, které byly předány do inicializace (v parametru metody Steward.AppInit().
        /// Výstupem je soupis typů konkrétních poskytovatelů této služby.
        /// Pozor: tato metoda vrací typy pro všechny poskytovatele, bez ohledu na jejich aktivitu. Tato metoda totiž nepracuje s instancemi poskytovatelů.
        /// </summary>
        /// <param name="service">Typ služby, například typeof(IGraphElementPainter)</param>
        /// <returns></returns>
        public static IEnumerable<Type> GetServiceTypeList(Type service)
        {
            return Current._GetServiceTypeList(service);
        }
        /// <summary>
        /// Vrátí soupis SKUTEČNÝCH OBJEKTŮ poskytovatelů služeb daného druhu.
        /// Vstupem je typ služby = určení interface, který má služba (servisní třída) nabízet.
        /// Tento typ služby musí být jedním z těch, které byly předány do inicializace (v parametru metody Steward.AppInit().
        /// Výstupem je soupis objektů konkrétních poskytovatelů této služby.
        /// Pokud jsem v NonDebug modu, pak jsou vráceni jen aktivní poskytovatelé (IPlugin.Active), v Debug modu pak všichni poskytovatelé.
        /// </summary>
        /// <typeparam name="S">Typ služby, například IGraphElementPainter</typeparam>
        /// <param name="cached">Příznak, zda je možno číst objekty z cache</param>
        /// <returns>Soupis objektů</returns>
        public static IEnumerable<S> GetServiceObjectList<S>(bool cached)
        {
            return Current._GetServiceObjectList<S>(cached);
        }
        /// <summary>
        /// Vrátí objekt jednoho konkrétního poskytovatele služby daného druhu.
        /// Vstupem je typ služby = určení interface, který má služba (servisní třída) nabízet.
        /// Tento typ služby musí být jedním z těch, které byly předány do inicializace (v parametru metody Steward.AppInit().
        /// Výstupem je konkrétní objekt = poskytovatel této služby. Zde nemá vliv to, zda je poskytovatel aktivní (IPlugin.Active).
        /// </summary>
        /// <typeparam name="S">Typ služby, například IGraphElementPainter</typeparam>
        /// <param name="providerType">Typ konkrétního poskytovatele služby, například typeof(PainterGanttRectangleCls), či spíš typ načtený z metody GetServiceTypeList()</param>
        /// <param name="cached">Příznak, zda je možno číst objekt z cache</param>
        /// <returns>Konkrétní poskytovatel anebo null</returns>
        public static S GetServiceObject<S>(Type providerType, bool cached)
        {
            return Current._GetServiceObject<S>(providerType, cached);
        }
        /// <summary>
        /// Obsahuje FileInfo pro hlavní soubor aplikace (spuštěný exe soubor, v němž se nachází Steward)
        /// </summary>
        public static FileInfo MainExeFile
        {
            get
            {
                if (Current._MainExeFile == null)
                    Current._MainExeFile = new FileInfo(Assembly.GetExecutingAssembly().Location);     // Plné jméno souboru, který se právě vykonává ("directory/Lcs.TraceVisualiser.exe")
                return Current._MainExeFile;
            }
        }
        /// <summary>
        /// Obsahuje adresář k souboru, kde leží aplikace
        /// </summary>
        public static string MainExePath
        {
            get
            {
                FileInfo exeFile = MainExeFile;
                if (exeFile == null) return "";
                return Path.GetDirectoryName(exeFile.FullName);
            }
        }
        #region Služby (services : IPlugin): privátní metody refexe, vyhledání, zmapování, uložení do cache
        /// <summary>
        /// Pomocí reflexe načte seznamy datových typů, které mohou sloužit jako Services (funkce, extendery, paintery, a kdovíco dalšího)
        /// </summary>
        private void _ServicesLoad()
        {
            // Příprava:
            lock (this._ServiceWriteLock)
            {
                try
                {
                    Type[] serviceTypes = _ServicesFindInterfaceList();
                    this._ServiceMaps = new Dictionary<Type, Dictionary<Type, object>>();
                    this._ServicesLoadAdd(serviceTypes);
                }
                catch (TypeLoadException ldx)
                {
                    Dialog.ShowError(ldx, "Při načítání seznamu pluginů došlo k chybě " + ldx.Message + ": " + ldx.TypeName);
                }
                catch (System.Reflection.ReflectionTypeLoadException rtx)
                {
                    Dialog.ShowError(rtx, "Při načítání seznamu pluginů došlo k chybě " + rtx.Message + ": " + rtx.LoaderExceptions[0].Message);
                }
                catch (Exception exc)
                {
                    Dialog.ShowError(exc, "Při načítání seznamu pluginů došlo k chybě " + exc.Message + ".");
                }
            }
        }
        /// <summary>
        /// Metoda najde všechny interface, které jsou potomkem IPlugin, a vrátí jejich seznam.
        /// Hledá pouze v namespace Noris.Schedule.Support.Services.
        /// </summary>
        /// <returns></returns>
        private Type[] _ServicesFindInterfaceList()
        {
            List<Type> serviceTypes = new List<Type>();
            Type iPlugin = typeof(IPlugin);

            // Vyhledám všechny typy v aktuální assembly 
            //  (protože budu hledat zdejší interface : IPlugin, s nimiž může this assembly jmenovitě pracovat: v takovém případě mě nezajímají potomci IPlugin v jiných assembly!)
            Type[] types = Assembly.GetAssembly(this.GetType()).GetTypes();
            foreach (Type iType in types)
            {
                if (!iType.IsInterface) continue;

                // Najdu všechny interface (do foundTypes), které daný datový typ implementuje:
                Type[] foundTypes = iType.FindInterfaces(
                    delegate(Type t, object data)
                    {	// Anonymní delegát dostává postupně všechny interface, které datový typ implementuje.
                        // Má vrátit true, pokud máme konkrétní interface zařadit do výstupu:
                        return (t == ((Type)data));
                    }, iPlugin);
                if (foundTypes.Length > 0)
                    serviceTypes.Add(iType);
            }

            return serviceTypes.ToArray();
        }
        /// <summary>
        /// Pomocí reflexe doplní existující seznam this._ServiceMaps o další služby a jejich poskytovatele.
        /// Zajistí načtení (reflexí fyzických knihoven) těch služeb, které jsou požadované, a přitom dosud nejsou načtené (ty už nedohledává).
        /// </summary>
        private void _ServicesLoadAdd(IEnumerable<Type> findServiceTypes)
        {
            Hashtable findTypes = new Hashtable();                               // Pomocný slovník, který si pro každý typ uloží příznak, zda typ implementuje dané interface
            foreach (Type service in findServiceTypes)
            {
                if (this._ServiceMaps.ContainsKey(service)) continue;            // Nonduplicitně!
                this._ServiceMaps.Add(service, new Dictionary<Type, object>());
                findTypes.Add(service, null);                                    // Pro QuickSearch v anonymním delegátovi
            }

            // Následující smyčka a rychlost:
            // Načtení mapy typů ze dvou knihoven (Schedule a Support) s celkovým počtem typů = 500 trvá 50 milisekund.
            IEnumerable<FileInfo> assemblyFiles = this._FindAssemblyFiles();
            foreach (FileInfo fileInfo in assemblyFiles)
            {
                Type[] assemblyTypes = null;
                try
                {
                    Assembly assembly = Assembly.LoadFrom(fileInfo.FullName);    // ReflectionOnlyLoadFrom vrhá chybu: Cannot resolve dependency to assembly 'System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' because it has not been preloaded. When using the ReflectionOnly APIs, dependent assemblies must be pre-loaded or loaded on demand through the ReflectionOnlyAssemblyResolve event."
                    assemblyTypes = assembly.GetTypes();                         // Soupis všech typů (třídy, struktury, interface, enumy, atd atd)
                }
                catch (Exception exc)
                {
                    assemblyTypes = null;
                    string innerTxt = "";
                    if (exc is System.Reflection.ReflectionTypeLoadException)
                    {
                        System.Reflection.ReflectionTypeLoadException excRefl = exc as System.Reflection.ReflectionTypeLoadException;
                        if (excRefl != null && excRefl.LoaderExceptions != null)
                        {
                            foreach (Exception excInn in excRefl.LoaderExceptions)
                                innerTxt += Environment.NewLine + Environment.NewLine + excInn.Message;
                        }
                    }
                    Msg.Error("Došlo k chybě při načítání assembly ze souboru: " + fileInfo.Name + Environment.NewLine + exc.Message + innerTxt);
                }
                if (assemblyTypes != null)
                {
                    foreach (Type dataType in assemblyTypes)
                    {
                        // Přebíráme pouze class, kter= nejsou abstract:
                        if (!dataType.IsClass) continue;
                        if (dataType.IsAbstract) continue;

                        // Najdu všechny interface (do foundTypes), které daný datový typ implementuje:
                        Type[] foundTypes = dataType.FindInterfaces(
                            delegate(Type t, object data)
                            {	// Anonymní delegát dostává postupně všechny interface, které datový typ implementuje.
                                // Má vrátit true, pokud máme konkrétní interface zařadit do výstupu:
                                return (((Hashtable)data).ContainsKey(t));
                            }, findTypes);

                        // V poli foundTypes mám typy = interface, které implementuje datový typ, a na které jsme se ptali.
                        foreach (Type implementedInterface in foundTypes)
                            this._ServiceMaps[implementedInterface].Add(dataType, null);
                    }
                }
            }
        }
        /// <summary>
        /// Vrátí seznam souborů (FileInfo) DLL a EXE v adresáři programu aplikace a v jeho podadresáři "Visualisers".
        /// </summary>
        /// <returns></returns>
        private IEnumerable<FileInfo> _FindAssemblyFiles()
        {
            List<FileInfo> result = new List<FileInfo>();

            string thisFile = MainExeFile.FullName;                           // FullName  : "C:\Program Files\Bla\TraceAnalyzer\TraceVisualiser.exe"
            string thisPath = Path.GetDirectoryName(thisFile);                // Adresář   : "C:\Program Files\Bla\TraceAnalyzer\"
            string thisName = Path.GetFileNameWithoutExtension(thisFile);     // Holé jméno: "TraceVisualiser"

            _FindAssemblyFilesInPath(thisPath, thisName, result);             // Najdu assemblies v adresáři spouštěné aplikace, jejichž jméno začíná stejně jako aplikace

            string pluginPath = Path.Combine(thisPath, "Visualisers");        // Podadresář pluginů vizualiseru
            _FindAssemblyFilesInPath(pluginPath, null, result);               // Najdu assemblies v adresáři pluginů, libovolného jména

            result.Sort(this._FindAssemblyFilesSorter);

            return result;
        }
        /// <summary>
        /// Najde 
        /// </summary>
        /// <param name="scanPath"></param>
        /// <param name="result"></param>
        private void _FindAssemblyFilesInPath(string scanPath, string startName, List<FileInfo> result)
        {
            DirectoryInfo dirInfo = new DirectoryInfo(scanPath);
            if (!dirInfo.Exists) return;

            FileInfo[] files = dirInfo.GetFiles("*.*", SearchOption.TopDirectoryOnly);
            foreach (FileInfo fileInfo in files)
            {
                string ext = fileInfo.Extension.ToLower();
                bool isExecutable = (ext == ".dll" || ext == ".exe");
                if (!isExecutable) continue;

                string name = Path.GetFileNameWithoutExtension(fileInfo.Name);  // Holé jméno bez přípony: "TraceVisualiser" nebo "TraceVisualiser.Green"
                bool isThisApp = String.IsNullOrEmpty(startName) || name.StartsWith(startName, StringComparison.OrdinalIgnoreCase);
                if (isThisApp)
                    result.Add(fileInfo);
            }
        }
        /// <summary>
        /// Setřídí assembly tak, jak se mají načítat do paměti (nejprve support, pak nadstavby).
        /// Nahrazuje de facto References na projektech a BuildOrder v solution.
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        private int _FindAssemblyFilesSorter(FileInfo a, FileInfo b)
        {
            int cmp = _FindAssemblyFilesGetPriority(a).CompareTo(_FindAssemblyFilesGetPriority(b));   // Porovná prioritu
            if (cmp != 0) return cmp;
            return String.Compare(a.Name, b.Name, true);       // Porovná název souboru (bez adresáře).
        }
        /// <summary>
        /// Vrátí číslo 1 pro souor, který je souborem current assembly, anebo vrátí číslo 3 pro ostatní soubory.
        /// Zajistí tak setřídění tak, že nejprve bude this a pak ostatní.
        /// </summary>
        /// <param name="fileInfo"></param>
        /// <returns></returns>
        private int _FindAssemblyFilesGetPriority(FileInfo fileInfo)
        {
            if (String.Equals(fileInfo.FullName, this._MainExeFile.FullName, StringComparison.OrdinalIgnoreCase))
                return 1;
            return 3;
        }
        /// <summary>
        /// Najde a vrátí soupis TYPŮ poskytovatelů dané služby.
        /// Pokud požadovaná služba není v interním seznamu (nebyla přítomna při inicializaci),
        /// dohledá její poskytovatele nyní. Ztráta je jen časová. Příště už budou načteny i data pro tuto novou službu.
        /// </summary>
        /// <param name="service"></param>
        /// <returns></returns>
        private IEnumerable<Type> _GetServiceTypeList(Type service)
        {
            Dictionary<Type, object> providers = _GetServiceProviders(service);
            if (providers == null) return null;
            return new List<Type>(providers.Keys);
        }
        /// <summary>
        /// Najde a vrátí soupis INSTANCÍ poskytovatelů dané služby.
        /// Vrací soupis objektů, které jsou vytvořeny z typu konkrétního poskytovatele.
        /// Pokud požadovaná služba není v interním seznamu (nebyla přítomna při inicializaci),
        /// dohledá její poskytovatele nyní. Ztráta je jen časová. Příště už budou načteny i data pro tuto novou službu.
        /// </summary>
        /// <typeparam name="S">Typ služby = typ interface, který má služba implementovat. Vrací objekty typované tímto typem.</typeparam>
        /// <param name="cached">true = umožní číst objekty z cache / false = chce vždy čerstvé objekty. Pozor, čas!!!</param>
        /// <returns></returns>
        private IEnumerable<S> _GetServiceObjectList<S>(bool cached)
        {
            Type service = typeof(S);                      // Typ služby
            List<S> result = new List<S>();                // Výstup
            bool onlyActive = !Steward.DebugMode;          // true = chci jen aktivní pluginy (nejsem v debug modu), false = chci všechny pluginy (i neaktivní) = jsem v debug modu

            Dictionary<Type, object> providers = _GetServiceProviders(service);
            if (providers == null) return null;
            IEnumerable<Type> types = new List<Type>(providers.Keys);
            foreach (Type type in types)
            {
                if (cached)
                {	// Objekty z cache (anebo nové do cache a pak z cache ven):
                    // Poznámka: v cache (providers) vždy existuje položka pro klíč (type), 
                    //     protože seznam typů (types) jsme načetli z tohoto soupisu. 
                    // Důvod: Dictionary nelze procházet cyklem foreach() a přitom do ní vepisovat nová data.
                    if (providers[type] == null)
                        providers[type] = ObjectGenerator<S>(type, true);

                    S provider = (S)providers[type];
                    if (this._IsProviderActive(provider, onlyActive))
                        result.Add(provider);
                }
                else
                {	// Objekty do výstupu - vždy nové:
                    S provider = ObjectGenerator<S>(type, true);
                    if (this._IsProviderActive(provider, onlyActive))
                        result.Add(provider);
                }
            }
            return result;
        }
        /// <summary>
        /// Vrátí true, pokud daný provider se má brát jako aktivní
        /// </summary>
        /// <param name="provider"></param>
        /// <param name="onlyActive"></param>
        /// <returns></returns>
        private bool _IsProviderActive(object provider, bool onlyActive)
        {
            if (provider == null) return false;         // Pokud provider neexistuje, nemůže být aktivní (=false)
            if (!onlyActive) return true;               // Pokud mám brát všechny bez ohledu na aktivitu, pak vrátím OK (=true)

            IPlugin plugin = provider as IPlugin;       // Podívám se na providera jako na IPlugin...
            if (plugin == null) return true;            // Provider není IPlugin => nelze určit jeho aktivitu, budu jej brát jako aktivní a vrátím true

            return plugin.Active;                       // Je to IPlugin a mám reagovat na jeho aktivitu.
        }
        /// <summary>
        /// Vrátí objekt jednoho konkrétního poskytovatele služby daného druhu.
        /// Vstupem je typ služby = určení interface, který má služba (servisní třída) nabízet.
        /// Tento typ služby musí být jedním z těch, které byly předány do inicializace (v parametru metody Steward.AppInit().
        /// Výstupem je soupis objektů konkrétních poskytovatelů této služby.
        /// </summary>
        /// <typeparam name="S">Typ služby, například IGraphElementPainter</typeparam>
        /// <param name="providerType">Typ konkrétního poskytovatele služby, například typeof(PainterGanttRectangleCls), či spíš typ načtený z metody GetServiceTypeList()</param>
        /// <param name="cached">Příznak, zda je možno číst objekt z cache</param>
        /// <returns>Konkrétní poskytovatel anebo null</returns>
        private S _GetServiceObject<S>(Type providerType, bool cached)
        {
            Type service = typeof(S);                      // Typ služby

            // Najdu seznam služeb požadovaného typu S: (jde o soupis typů a cachovaných objektů providerů):
            Dictionary<Type, object> providers = _GetServiceProviders(service);
            if (providers == null) return default(S);

            // Najdu konkrétního providera, pokud nebude v seznamu tak ho ani nebudu vytvářet, nejspíš jde o jiný typ než je přípustné:
            object provider = null;
            if (!providers.TryGetValue(providerType, out provider)) return default(S);

            // Z cache / do cache anebo nový objekt:
            if (cached)
            {	// Objekty z cache (anebo nové do cache a pak z cache ven):
                // Poznámka: v cache (providers) vždy existuje položka pro klíč (type), 
                //     protože seznam typů (types) jsme načetli z tohoto soupisu. 
                //     Důvod: Dictionary nelze procházet cyklem foreach() a přitom do ní vepisovat nová data.
                if (provider == null)
                {
                    provider = ObjectGenerator<S>(providerType, true);
                    providers[providerType] = provider;
                }
            }
            else
            {	// Objekty do výstupu vždy nové:
                provider = ObjectGenerator<S>(providerType, true);
            }
            return (S)provider;
        }
        /// <summary>
        /// Metoda vrátí vnitřní dictionary pro danou službu (servis, IPlugin).
        /// Pro daný typ služby (předpis interface) vrací seznam providerů (reální poskytovatelé) = objekty (Typ a jeho cachovaná instance).
        /// Pokud služba není načtena, načte ji.
        /// Vrací živou referenci do interní paměti!
        /// </summary>
        /// <param name="service"></param>
        /// <returns></returns>
        private Dictionary<Type, object> _GetServiceProviders(Type service)
        {
            Dictionary<Type, object> providers;
            // Máme už načtené providery dané služby:
            if (this._ServiceMaps.TryGetValue(service, out providers)) return providers;

            // Nemáme => můžeme služby zamknout, zkusit je donačíst, atd:
            lock (this._ServiceWriteLock)
            {
                if (this._ServiceMaps.TryGetValue(service, out providers)) return providers;
                this._ServicesLoadAdd(new Type[] { service });
                if (this._ServiceMaps.TryGetValue(service, out providers)) return providers;
            }

            return null;
        }
        /// <summary>
        /// Unload služeb.
        /// Uvolní služby z cache. Uvolní cache.
        /// Pokud je služba IDisposable, zavolá její Dispose.
        /// </summary>
        private void _ServicesUnLoad()
        {
            lock (this._ServiceWriteLock)
            {
                // Provedeme Dispose na všechny objekty, které to mají rády (ale na každém jen jedenkrát):
                List<object> disposedProviders = new List<object>();     // Reference na objekty, pro které jsme už zavolali Dispose
                foreach (Dictionary<Type, object> providers in this._ServiceMaps.Values)
                {
                    foreach (object provider in providers)
                    {
                        if (provider == null) continue;                  // Pro tento typ nebyl provider cachován. Jdeme na další.
                        if (provider is IDisposable)
                        {	// Pokud je provider (krom jiného) i IDisposable:
                            bool wasDisposed = disposedProviders.Exists(delegate(object w) { return (Object.ReferenceEquals(w, provider)); });
                            if (!wasDisposed)
                            {	// A pokud ještě na tomto konkrétním objektu nebyl proveden Dispose:
                                disposedProviders.Add(provider);
                                ((IDisposable)provider).Dispose();
                            }
                        }
                    }
                }

                // Odebereme vše z paměti, o zbytek se postará GC:
                this._ServiceMaps.Clear();
            }
        }
        /// <summary>
        /// Vrátí nově vytvořený objekt daného typu.
        /// Pokud se nepodaří, vrátí null.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="type"></param>
        /// <returns></returns>
        public static T ObjectGenerator<T>(Type type, bool suppressException)
        {
            T result = default(T);
            try
            {
                object obj = System.Activator.CreateInstance(type, true);
                result = (T)obj;
            }
            catch (Exception)
            {
                result = default(T);
            }
            return result;
        }
        /// <summary>
        /// Vrátí seznam objektů
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="typeList"></param>
        /// <returns></returns>
        public static List<T> ActivateTypes<T>(IEnumerable<Type> typeList)
        {
            List<T> result = new List<T>();
            foreach (Type type in typeList)
                result.Add((T)System.Activator.CreateInstance(type, false));
            return result;
        }
        /// <summary>
        /// Jednotné úložiště reflektovaných typů instalovaných služeb.
        /// Obsahuje :
        /// Key = typ služby (reprezentovaný typem interface)
        /// Value = seznam typů, které tuto službu poskytují 
        /// Seznam typů: Key = typ fyzické implementace, Value = cache instance služby nebo null.
        /// </summary>
        private Dictionary<Type, Dictionary<Type, object>> _ServiceMaps;
        /// <summary>
        /// Zámek na zápisy do _ServiceMaps
        /// </summary>
        private object _ServiceWriteLock;
        /// <summary>
        /// FileInfo o main assembly
        /// </summary>
        private FileInfo _MainExeFile;
        #endregion
        #endregion
		#region Static služby
        /// <summary>
        /// Vytvoří a otevře dané okno v bloku try-catch, případnou chybu zobrazí a utopí.
        /// Lze použít i s delegátem na anonymní metodu, která se vykoná s inicializovaným oknem před jeho otevřením.
        /// Typicky: 
        /// Steward.OpenFormInTryCatch(typeof(NašeOkno), "Chybová hláška %0", delegate { akce s oknem; })  (kde %0 v chybové hlášce se nahradí textem chyby).
        /// Metoda vrací 
        /// </summary>
        /// <param name="formType">Typ okna</param>
        /// <param name="errMsg"></param>
        /// <param name="action">Akce volaná před otevřením okna.
        /// Lze zadat buď jako lambda výraz (s parametrem form, bez návratové hodnoty), nebo jako delegáta: delegate(Form form) { akce1; akce2; akce3; }.
        /// Tato akce je rovněž zabalena v try-catch bloků, případná chyba bude odchycena a nahlášena.</param>
        public static DialogResult OpenDialogFormInTryCatch(Type formType, string errMsg, Action<Form> action)
        {
            DialogResult result = DialogResult.None;
            try
            {
                using (Form form = Steward.ObjectGenerator<Form>(formType, false))
                {
                    if (action != null)
                        action(form);
                    result = form.ShowDialog(Steward.MainForm);
                }
            }
            catch (Exception exc)
            {
                result = DialogResult.None;
                Dialog.ShowError(exc, errMsg.Replace("%0", exc.Message));
            }
            return result;
        }
        /// <summary>
        /// Vykoná daný blok kódu v bloku try-catch, případnou chybu zobrazí a utopí.
        /// Typicky se používá společně s delegátem na anonymní metodu:
        /// Steward.ExecuteInTryCatch(delegate { vlastní akce; }, "Chybová hláška %0")  (kde %0 v chybové hlášce se nahradí textem chyby).
        /// Pro otevření okna se lépe hodí metoda Steward.OpenDialogFormInTryCatch() nebo Steward.OpenNonModalFormInTryCatch().
        /// </summary>
        /// <param name="action"></param>
        /// <param name="errMsg"></param>
		public static void ExecuteInTryCatch(Action action, string errMsg)
		{
			try
			{
				action();
			}
			catch (Exception exc)
			{
                if (errMsg != null)
    				Dialog.ShowError(exc, errMsg.Replace("%0", exc.Message));
			}
		}
        /// <summary>
        /// Vrátí sumární Message z chyby + jejích InnerException
        /// </summary>
        /// <param name="exc"></param>
        /// <returns></returns>
        public static string GetExceptionMessage(Exception exc)
        {
            string msg = "";
            Exception e = exc;
            while (e != null)
            {
                msg += (msg.Length == 0 ? "" : "; ") + e.Message;
                e = e.InnerException;
            }
            return msg;
        }
        /// <summary>
        /// Vrátí StackTrace z nejhlubší chyby
        /// </summary>
        /// <param name="exc"></param>
        /// <returns></returns>
        public static string GetInnerStack(Exception exc)
        {
            Exception e = exc;
            while (e != null && e.InnerException != null)
                e = e.InnerException;
            return (e == null ? "" : e.StackTrace);
        }
		#endregion
		#region Run Process
        /// <summary>
        /// Otevře nový Windows proces
        /// </summary>
        /// <param name="processName"></param>
        public static void RunProcess(string processName)
        {
            System.Diagnostics.ProcessStartInfo psi = new System.Diagnostics.ProcessStartInfo();
            psi.FileName = processName;
            System.Diagnostics.Process process = new System.Diagnostics.Process();
            process.StartInfo = psi;
            process.Start();
        }
		#endregion
		#region Tray ikona
        /// <summary>
        /// Zobrazí BaloonTip u Tray ikony aplikace
        /// </summary>
        /// <param name="text"></param>
        /// <param name="title"></param>
        /// <param name="icon"></param>
        public static void TrayShowTip(string text, string title, ToolTipIcon icon)
        {
            NotifyIcon trayIcon = TrayIcon;
            trayIcon.BalloonTipText = text;
            trayIcon.BalloonTipTitle = title;
            trayIcon.BalloonTipIcon = icon;
            trayIcon.Visible = true;
            trayIcon.ShowBalloonTip(250);
        }
        /// <summary>
        /// Přístup k Tray ikoně aplikace.
        /// </summary>
        public static NotifyIcon TrayIcon { get { return Current._TrayIcon; } }
        private void _TrayIconDispose()
        {
            if (__TrayIcon == null) return;
            NotifyIcon trayIcon = __TrayIcon;
            trayIcon.Visible = false;
            trayIcon.Dispose();
            __TrayIcon = null;
        }
        private void _TrayIconHide()
        {
            if (__TrayIcon == null) return;
            NotifyIcon trayIcon = __TrayIcon;
            trayIcon.Visible = false;
        }
        private NotifyIcon _TrayIcon
        {
            get
            {
                if (__TrayIcon == null)
                {
                    NotifyIcon trayIcon = new NotifyIcon();
                    trayIcon.Icon = Noris.Tools.TraceVisualiser.Pics.PicLibrary.TraceVisualiser;
                    trayIcon.Text = "TraceVisualiser";
                    __TrayIcon = trayIcon;
                }
                return __TrayIcon;
            }
        }
        private NotifyIcon __TrayIcon;
		#endregion
        #region Cache objektů
        /// <summary>
        /// Najde a vrátí v paměti objekt dané třídy. Pamatuje si výhradně jeden objekt za jednu třídu.
        /// Pokud ještě v paměti není, založí jej. Musí mít bezparametrický public konstruktor (internal nestačí).
        /// Pokud daná třída implementuje interface IInitialize, pak se na vytvořeném objektu zavolá metoda IInitialize.Initialize().
        /// Pokud daná třída implementuje interface IDisposable, pak se při ukončování života aplikace zavolá metoda IDisposable.Dispose() na všech vhodných objektech.
        /// Tam (v Dispose()) samozřejmě může třída jakkoliv persistovat svoje data, a při příštím startu je v metodě ICacheInitialize.Initialize() zpátky do sebe naplnit.
        /// </summary>
        /// <param name="typeOfObject"></param>
        /// <returns></returns>
        public static object GetFromCache(Type typeOfObject)
        {
            return Current._GetFromCache(typeOfObject);
        }
        /// <summary>
        /// Čtení / vytváření prvků v Cache
        /// </summary>
        /// <param name="typeOfObject"></param>
        /// <returns></returns>
        private object _GetFromCache(Type typeOfObject)
        {
            if (typeOfObject == null) return null;
            
            object c;
            if (_Cache.TryGetValue(typeOfObject, out c))
                return c;

            lock (_Cache)
            {
                if (_Cache.TryGetValue(typeOfObject, out c))
                    return c;
                
                c = System.Activator.CreateInstance(typeOfObject);
                if (c != null)
                {
                    if (c is IInitialize)
                        ((IInitialize)c).Initialize();

                    _Cache.Add(typeOfObject, c);
                }
                return c;
            }
        }
        /// <summary>
        /// Dispose na všechny prvky v paměti Cache, které Dispose podporují
        /// </summary>
        private void _CacheDispose()
        {
            lock (_Cache)
            {
                foreach (object c in _Cache.Values)
                {
                    if (c is IDisposable)
                        ((IDisposable)c).Dispose();
                }
                _Cache.Clear();
            }
        }
        private Dictionary<Type, object> _Cache;
        #endregion
        #region Auditlog
        /// <summary>
        /// Zaznamená chybovou hlášku do standardního auditlogu.
        /// </summary>
        /// <param name="message"></param>
        /// <param name="exc"></param>
        public static void AuditInfo(string message)
        {
            Audit(AuditRowType.Info, message, null);
        }
        /// <summary>
        /// Zaznamená chybovou hlášku do standardního auditlogu.
        /// </summary>
        /// <param name="message"></param>
        /// <param name="exc"></param>
        public static void AuditWarning(string message)
        {
            Audit(AuditRowType.Info, message, null);
        }
        /// <summary>
        /// Zaznamená chybovou hlášku do standardního auditlogu.
        /// </summary>
        /// <param name="message"></param>
        /// <param name="exc"></param>
        public static void Audit(AuditRowType auditType, string message, Exception exc)
        {
            string auditFile = Steward.AuditFile;
            string tab = "\t";
            string now = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.f");
            string text;

            // message:
            text = now + tab +
                _AuditGetInfoChar(auditType) + tab +
                _AuditGetText(message) + tab +
                "" + Environment.NewLine;
            System.IO.File.AppendAllText(auditFile, text, Encoding.UTF8);

            // exception:
            Exception ex = exc;
            while (ex != null)
            {
                string line = AuditGetErrorRow(ex);
                text = now + tab +
                    _AuditGetInfoChar(auditType) + tab +
                    _AuditGetText(ex.Message) + tab +
                    _AuditGetText(line) + tab + Environment.NewLine;
                System.IO.File.AppendAllText(auditFile, text, Encoding.UTF8);

                ex = ex.InnerException;
            }
        }
        private static string _AuditGetInfoChar(AuditRowType auditType)
        {
            switch (auditType)
            {
                case AuditRowType.None: return "N";
                case AuditRowType.Info: return "I";
                case AuditRowType.Warning: return "W";
                case AuditRowType.Completed: return "C";
                case AuditRowType.BreakError: return "B";
                case AuditRowType.InputError: return "D";
                case AuditRowType.ApplicationError: return "E";
                case AuditRowType.CoreError: return "S";
                case AuditRowType.SqlError: return "Q";
                case AuditRowType.MsNetError: return "M";
            }
            return "";
        }
        private static string _AuditGetText(string text)
        {
            if (String.IsNullOrEmpty(text)) return "";
            string result = text.Replace("\t", "  ");
            result = result.Replace("\r", "  ");
            result = result.Replace("\n", "  ");
            return result;
        }
        /// <summary>
        /// Najde a vrátí řádek programu, který pravděpodobně způsobil chybu
        /// </summary>
        /// <param name="stackTrace">Výpis Stacku v okamžiku chyby</param>
        /// <returns></returns>
        internal static string AuditGetErrorRow(Exception exc)
        {
            if (exc == null) return "";
            return AuditGetErrorRow(exc.StackTrace);
        }
        /// <summary>
        /// Najde a vrátí řádek programu, který pravděpodobně způsobil chybu
        /// </summary>
        /// <param name="stackTrace">Výpis Stacku v okamžiku chyby</param>
        /// <returns></returns>
        internal static string AuditGetErrorRow(string stackTrace)
        {
            if (stackTrace == null) return "";

            string[] stackLines = stackTrace.Split('\r', '\n');
            string errorPoint = "";

            foreach (string stackLine in stackLines)
            {
                string line = stackLine.Trim();
                if (line.Length == 0) continue;

                // Tyto texty signalizují, že jde o stacktrace který mě nezajímá (systémové vnitřnosti a vrhač chyb):
                string lineLower = line.ToLower();
                if (lineLower.Contains(" system.") ||
                    lineLower.Contains(" noris.schedule.support.throw."))
                    continue;

                errorPoint = line;
                break;
            }
            return errorPoint;
        }
        /// <summary>
        /// Pokud existuje soubor jména shodného, jaké má mít auditlog, pak takový soubor přejmenuje nebo smaže. 
        /// Prostě jej odstraní.
        /// </summary>
        private void _AuditClear()
        {
            string auditFile = AuditFile;
            string auditPath = Path.GetDirectoryName(auditFile);
            string auditExte = Path.GetExtension(auditFile);
            string auditNmwx = Path.GetFileNameWithoutExtension(auditFile);

            // Pojmenovat current auditlog soubor tak, aby měl za jménem suffix (datum, čas, [číslo]):
            if (!File.Exists(auditFile)) return;
            FileInfo current = new FileInfo(auditFile);
            string auditBase = Path.Combine(auditPath, auditNmwx);           // c:\adresář\errlog
            auditBase = auditBase + "_" + current.LastWriteTime.ToString("yyyyMMdd_HHmmss");

            string auditRename = null;
            for (int t = 0; t < 100; t++)
            {
                auditRename = auditBase + (t == 0 ? "" : t.ToString("D2")) + auditExte;
                if (!File.Exists(auditRename))
                    break;
                auditRename = null;
            }
            if (auditRename == null)
                File.Delete(auditFile);
            else
                File.Move(auditFile, auditRename);

            // Najít současné auditlogy a pokud je jich více, než MAX, pak ty přebytečné smazat:
            DirectoryInfo di = new DirectoryInfo(auditPath);
            FileInfo[] auditOldFiles = di.GetFiles(auditNmwx + "_*" + auditExte);
            int auditMax = 120;
            if (auditOldFiles.Length > auditMax)
            {
                List<FileInfo> oldFiles = new List<FileInfo>(auditOldFiles);
                oldFiles.Sort((a, b) => a.Name.CompareTo(b.Name));         // Setřídím podle jména (jméno nese datum, viz dříve proměnná auditBase)
                int last = oldFiles.Count - auditMax;                      // Smažu prvních tolik souborů, aby zbylo posledních (auditMax) položek:
                for (int ax = 0; ax < last; ax++)
                    File.Delete(oldFiles[ax].FullName);
            }
        }
        /// <summary>
        /// Soubor (holý název), do něhož se budou auditovat problémy a výsledky
        /// </summary>
        internal static string AuditName { get { return "auditlog.csv"; } }
        /// <summary>
        /// Soubor (včetně adresáře), do něhož se budou auditovat problémy a výsledky
        /// </summary>
        internal static string AuditFile { get { return _AuditGetFileName(); } }
        private static string _AuditGetFileName()
        {
            // Pozor: tento soubor následně může číst Noris, pokud tedy dojde ke změně v umístění, je nutno synchronně změnit metodu:
            // Noris.LCS.Manufacturing.SchedulerSupportCwl._ResultGetFileForApp() v App\Lcs\Manufacturing\SchedulerCwl.cs !
            string path = Path.Combine(MainExeFile.Directory.FullName, "Trace");
            if (!Directory.Exists(path))
                Directory.CreateDirectory(path);
            return Path.Combine(path, AuditName);
        }
        #endregion
    }
    #region ENUM AuditRowType
    /// <summary>
    /// Druhy informací v auditlogu
    /// </summary>
    public enum AuditRowType
    {
        /// <summary>
        /// N: neurčený typ
        /// </summary>
        None = 1,
        /// <summary>
        /// I: Informace
        /// </summary>
        Info,
        /// <summary>
        /// W: Warning
        /// </summary>
        Warning,
        /// <summary>
        /// C: úspěšně dokončená operace
        /// </summary>
        Completed,
        /// <summary>
        /// B: break, přerušeno uživatelem
        /// </summary>
        BreakError,
        /// <summary>
        /// D: datová chyba na vstupním formuláři/vstupních parametrech
        /// </summary>
        InputError,
        /// <summary>
        /// E: Error, aplikační
        /// </summary>
        ApplicationError,
        /// <summary>
        /// S: Systémová chyba vrstvy, typicky vrstva Support nebo chyba sekvence operací, řízení programu (uživatel nemůže ovlivnit)
        /// </summary>
        CoreError,
        /// <summary>
        /// Q: SQL chyba, hlášená ze SQL operací
        /// </summary>
        SqlError,
        /// <summary>
        /// M: chyba neodchycená jinak než jako Exception, chyby .NET
        /// </summary>
        MsNetError
    }
    #endregion
}
